package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.PinYinUtils;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.search.runable.RebuildDistroBloomFilterRunnable;
import com.atguigu.tingshu.common.factory.ScheduleTaskThreadFactory;
import com.atguigu.tingshu.search.repository.AlbumInfoRepository;
import com.atguigu.tingshu.search.repository.SuggestInfoRepository;
import com.atguigu.tingshu.search.service.ItemService;
import com.atguigu.tingshu.search.thred.ExpireThread;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import jakarta.annotation.PostConstruct;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class ItemServiceImpl implements ItemService {
    @Qualifier("com.atguigu.tingshu.album.client.AlbumInfoFeignClient")
    @Autowired
    private  AlbumInfoFeignClient albumInfoFeignClient;
    @Qualifier("com.atguigu.tingshu.user.client.UserInfoFeignClient")
    @Autowired
    private  UserInfoFeignClient userInfoFeignClient;
    @Autowired
    private  AlbumInfoRepository albumInfoRepository;
    @Autowired
    private  SuggestInfoRepository suggestInfoRepository;
    @Autowired
    private  ElasticsearchClient elasticsearchClient;
    @Autowired
    private  StringRedisTemplate redisTemplate;
    @Autowired
    private  ThreadPoolExecutor threadPoolExecutor;
    @Autowired
    private  RBloomFilter rBloomFilter;
    @Autowired
    private  RedissonClient redissonClient;
    @Autowired
    private  ItemServiceImpl itemServiceImpl;
    static ExecutorService executorService = null;
    HashMap<String, Object> localCache = new HashMap<>();
    ThreadLocal<String> deepAcquireLockTokenMap = new ThreadLocal<>();

    static {
        executorService = Executors.newFixedThreadPool(4);
    }
//    /*
//    构造器装配
//     */
//    public ItemServiceImpl(@Qualifier("com.atguigu.tingshu.album.client.AlbumInfoFeignClient") AlbumInfoFeignClient albumInfoFeignClient, @Qualifier("com.atguigu.tingshu.user.client.UserInfoFeignClient") UserInfoFeignClient userInfoFeignClient, AlbumInfoRepository albumInfoRepository, SuggestInfoRepository suggestInfoRepository, ElasticsearchClient elasticsearchClient, StringRedisTemplate redisTemplate, ThreadPoolExecutor threadPoolExecutor, RBloomFilter rBloomFilter, RedissonClient redissonClient) {
//        this.albumInfoFeignClient = albumInfoFeignClient;
//        this.userInfoFeignClient = userInfoFeignClient;
//        this.albumInfoRepository = albumInfoRepository;
//        this.suggestInfoRepository = suggestInfoRepository;
//        this.elasticsearchClient = elasticsearchClient;
//        this.redisTemplate = redisTemplate;
//        this.threadPoolExecutor = threadPoolExecutor;
//        this.rBloomFilter = rBloomFilter;
//        this.redissonClient = redissonClient;
//    }

    /**
     * 定时/延时重建布隆过滤器
     */
    @PostConstruct
    public void rebuildBloomFilter() {
//         定时执行
//         ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
//        scheduledExecutorService.scheduleWithFixedDelay(new RebuildDistroBloomFilterRunnable(redissonClient, itemServiceImpl, redisTemplate),0,1,TimeUnit.MINUTES);

        // 延时方式(只会执行一次)
//        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
//        scheduledExecutorService.schedule(new RebuildDistroBloomFilterRunnable(redissonClient, itemServiceImpl, redisTemplate),3,TimeUnit.MINUTES);
        ScheduleTaskThreadFactory instance = ScheduleTaskThreadFactory.getInstance();
        long diffTime = instance.getDiffTime(System.currentTimeMillis(),7,2,0,0);
        instance.execute(new RebuildDistroBloomFilterRunnable(redissonClient, itemServiceImpl, redisTemplate), diffTime, TimeUnit.MILLISECONDS);
    }

    /**
     * 优化方式 使用线程池+countdownlatch使用
     * 为什么第一次比较耗时：后面不怎么耗时：
     * 1、线程池来说，第一批任务进来需要创建线程（创建线程会耗时 但是耗时不多）
     * 2、远程调用的时候缓存机制。
     * 当客户端第一次远程调用服务端时，openfeign的底层会从远程的注册中心上获取对方的信息。（缓存到客户端本地）而当客户端后面每次调用服务端时，直接从客户端本地内存（Map）中获取  不在请求远端注册中心。
     * 服务发现（OpenFeign采用了缓存思想）缓存是最能提高性能。异步提高效率很低。多线程上下文切换（很耗时） Redis(单线程模型解决读写操作)、
     * Redis高性能的原因：
     * 1、Redis采用了单线程模型
     * 2、Redis客户端采用多路复用机制 select/epoll模型
     * 3、Redis内部拥有高效的数据结构（Hash 树  跳跃表 ）
     * 4、Redis中的数据都保存在内存中。
     * @param albumId 专辑id
     */
    public void onAlbumEs(Long albumId) {

        // 局部变量(不能作为一个成员变量 不然在多线程情况下出现数据不一致问题){线程共享}
        CountDownLatch countDownLatch = new CountDownLatch(4);


        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
        Long startTime = System.currentTimeMillis();

        Future<Long> longFutureUserId = executorService.submit(new Callable<Long>() {
            @Override
            public Long call() throws Exception {
                // 远程调用专辑微服务获取专辑基本信息
                Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlumInfo(albumId);
                AlbumInfo albumInfo = albumInfoResult.getData();
                if (albumInfo == null) {
                    throw new GuiguException(201, "搜索微服务调用专辑微服务获取专辑基本信息失败");
                }
                albumInfoIndex.setId(albumInfo.getId());
                albumInfoIndex.setAlbumTitle(albumInfo.getAlbumTitle());
                albumInfoIndex.setAlbumIntro(albumInfo.getAlbumIntro());
                albumInfoIndex.setCoverUrl(albumInfo.getCoverUrl());
                albumInfoIndex.setIncludeTrackCount(albumInfo.getIncludeTrackCount());
                albumInfoIndex.setIsFinished(albumInfo.getIsFinished().toString());
                albumInfoIndex.setPayType(albumInfo.getPayType());
                List<AttributeValueIndex> attributeValueIndexList = albumInfo.getAlbumAttributeValueVoList().stream().map(albumAttributeValue -> {
                    AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                    attributeValueIndex.setAttributeId(albumAttributeValue.getAttributeId());
                    attributeValueIndex.setValueId(albumAttributeValue.getValueId());
                    return attributeValueIndex;
                }).collect(Collectors.toList());
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
                albumInfoIndex.setCreateTime(new Date()); // 专辑在es中的创建时间
                countDownLatch.countDown();
                return albumInfo.getUserId();
            }
        });

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                // 远程调用专辑微服务获取专辑分类信息
                Result<BaseCategoryView> baseCategoryViewResult = albumInfoFeignClient.getAlbumInfoCategory(albumId);
                BaseCategoryView baseCategoryView = baseCategoryViewResult.getData();
                if (baseCategoryView == null) {
                    throw new GuiguException(201, "搜索微服务调用专辑微服务获取专辑分类信息失败");
                }
                albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
                albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
                albumInfoIndex.setCategory3Id(baseCategoryView.getCategory3Id());
                countDownLatch.countDown();
            }
        });

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    // 远程调用用户微服务获取用户信息
                    Long userId = longFutureUserId.get();
                    Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfo(userId);
                    UserInfoVo userInfoVo = userInfoVoResult.getData();
                    if (userInfoVo == null) {
                        throw new GuiguException(201, "搜索微服务调用专辑微服务获取用户信息失败");
                    }
                    albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
                    countDownLatch.countDown();
                } catch (Exception e) {
                    log.error("当前线程阻塞期间出现了异常：{}", e.getMessage());
                }
            }
        });


        executorService.execute(new Runnable() {
            @Override
            public void run() {
                // 远程调用专辑微服务获取专辑的统计信息
                Result<AlbumStatVo> albumStatVoResult = albumInfoFeignClient.getAlbumInfoStat(albumId);
                AlbumStatVo albumStatVo = albumStatVoResult.getData();
                if (albumStatVo == null) {
                    throw new GuiguException(201, "搜索微服务调用专辑微服务获取专辑统计信息失败");
                }
                albumInfoIndex.setCommentStatNum(albumStatVo.getCommentStatNum());
                albumInfoIndex.setBuyStatNum(albumStatVo.getBuyStatNum());
                albumInfoIndex.setPlayStatNum(albumStatVo.getPlayStatNum());
                albumInfoIndex.setSubscribeStatNum(albumStatVo.getSubscribeStatNum());

//        Double hotScore = albumStatVo.getPlayStatNum()
//                * 0.4 + albumStatVo.getSubscribeStatNum()
//                * 0.1 + albumStatVo.getBuyStatNum()
//                * 0.3 + albumStatVo.getCommentStatNum() * 0.2;
                Double hotScore = new Random().nextDouble();
                albumInfoIndex.setHotScore(hotScore);
                countDownLatch.countDown();
            }
        });
        try {
            // 让当前线程也即tomcat线程池的线程阻塞
            countDownLatch.await();
            //  将文档对象存储到Es中
            AlbumInfoIndex flag = albumInfoRepository.save(albumInfoIndex);// tomcat线程池的线程执行
            Long endTime = System.currentTimeMillis();
            log.info("专辑：{}存储到Es中：{}:耗时：{}ms", albumId, flag != null ? "success" : "fail", endTime - startTime);
        } catch (InterruptedException e) {
            log.error("当前阻塞期间出现了异常:{}", e.getMessage());
        }
        //保存到搜索提示库中
        SuggestIndex suggestIndex = new SuggestIndex();
        suggestIndex.setId(albumId.toString());
        suggestIndex.setTitle(albumInfoIndex.getAlbumTitle());
        suggestIndex.setKeyword(new Completion(new String[]{albumInfoIndex.getAlbumTitle()}));
        suggestIndex.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumTitle())}));
        suggestIndex.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAlbumTitle())}));
        suggestInfoRepository.save(suggestIndex);
    }

    @Override
    public void offAlbumEs(Long albumId) {
        albumInfoRepository.deleteById(albumId);
    }

    @Override
    public void onAlbumEsList() {
//        Long userId = AuthContextHolder.getUserId();
        Result<List<Long>> albumInfoIdList = albumInfoFeignClient.getAlbumInfoIdList(1L);
        List<Long> albumInfoIdListData = albumInfoIdList.getData();
        if (albumInfoIdListData == null) {
            throw new GuiguException(201, "搜索微服务调用专辑微服务获取专辑Id列表失败");
        }
        for (Long albumInfoId : albumInfoIdListData) {
            this.onAlbumEs(albumInfoId);
        }
    }

    @Override

    public Map<String, Object> getAlbumDetail(Long albumId) {
        return getAlbumInfoFromDb(albumId);
    }

    /**
     * Redisson版本的分布式布隆+分布式锁(非阻塞的续期机制)
     * 解决缓存穿透以及缓存击穿
     * version1:redis版本（很多场景都要展开说）
     * version2:redisson版本（lock trylock  续期如何实现）
     * @return Map
     */
    public Map opsDistroCacheAndBloomFilterRedissonVersion(Long albumId) {
        // 定义变量
        // 定义缓存key
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        // 定义锁key
        String lockKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;
        boolean contains = rBloomFilter.contains(albumId);
        if (!contains) {
            return null;
        }
        String jsonFromRedis = redisTemplate.opsForValue().get(cacheKey);
        if (StringUtils.hasText(jsonFromRedis)) {
            //缓存命中
            return JSONObject.parseObject(jsonFromRedis, Map.class);
        }
        //缓存未命中
        //获取锁对象
        RLock lock = redissonClient.getLock(lockKey);
        boolean isGetLock = lock.tryLock();//抢锁
        if (isGetLock) {
            try {
                //回源
                Map<String, Object> albumInfoFromDb = itemServiceImpl.getAlbumInfoFromDb(albumId);
                //同步数据到redis中 解决缓存穿透的固定值攻击
//            if (albumInfoFromDb == null ||albumInfoFromDb.size() == 0){
//
//            }
                Long ttl = 0L;
                if (albumInfoFromDb == null || albumInfoFromDb.size() == 0) {
                    ttl = 30L;
                } else {
                    ttl = 60 * 24 * 7L;
                }
                redisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb), ttl, TimeUnit.MINUTES);
                return albumInfoFromDb;
            } finally {
                lock.unlock();
            }
        } else {
            //未抢到锁
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            String resultFromCache  = redisTemplate.opsForValue().get(cacheKey);
            return JSONObject.parseObject(resultFromCache, Map.class);
        }
    }

    /**
     * 分布式缓存+解决缓存击穿使用分布式锁（Redis版本） 自带自动续期功能 + 布隆过滤器
     * 守护线程特点：只要jvm中还存在用户线程，守护线程会一直工作。
     * 只要jvm中没有用户线程 守护线程就不在工作
     *
     * @param albumId 专辑id
     * @return Map<String, Object>
     */
    public Map<String, Object> opsDistroCacheAndCacheHit(Long albumId) {
        //定义缓存key
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        //定义锁的key
        String lockKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;
        //抢锁标识
        Boolean lockFlag = false;
        //删锁标识
        String token = null;
        // 0.查询分布式布隆过滤器
        boolean contains = rBloomFilter.contains(albumId);
        if (!contains) {
            return null;
        }
        //查询缓存
        String jsonFromRedis = redisTemplate.opsForValue().get(cacheKey);
        //缓存命中
        if (StringUtils.hasText(jsonFromRedis)) {
            return JSONObject.parseObject(jsonFromRedis, Map.class);
        }
        //缓存未命中
        token = deepAcquireLockTokenMap.get();
        if (!StringUtils.hasText(token)) {
            token = UUID.randomUUID().toString().replace("-", "");
            //抢分布式锁(原子性)
            lockFlag = redisTemplate.opsForValue().setIfAbsent(lockKey, token, 3, TimeUnit.SECONDS);
        }
        if (lockFlag) {
            // 启动一个续期线程完成续期
            ExpireThread expireThread = new ExpireThread(redisTemplate);
            expireThread.expire(lockKey, 1L, 3L);
            Map<String, Object> albumInfoFromDb = new HashMap<>();
//            try {
//            试验续期
//                Thread.sleep(100000);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }
            try {
                //抢到锁,查询数据并同步到缓存中后返回
                albumInfoFromDb = getAlbumInfoFromDb(albumId);
                redisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb));
                //释放锁(使用原子性)
//            if (token.equals(redisTemplate.opsForValue().get(lockKey))) {
//                redisTemplate.delete(lockKey);
//            }
                String script = "if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                Long execute = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), List.of(lockKey), token);
                if (execute == 0) {
                    log.error("删除锁失败");
                } else {
                    log.info("删除锁成功");
                }
            } finally {
                deepAcquireLockTokenMap.remove();
                expireThread.stop();
            }

            return albumInfoFromDb;
        } else {
            // 未抢到锁  混合使用：策略一（查询缓存）+策略二（自旋+递归）
            // 通过自旋抢锁 解决无效递归 减少栈溢出风险   cpu会很快被打满
//            Thread.sleep(200);
            // 解决百分之99的正常情况
            // 一次查询缓存：为了解决正常情况下抢不到锁的线程从缓存中直接查询数据返回
            String result = redisTemplate.opsForValue().get(cacheKey);
            if (StringUtils.hasText(result)) {
                return JSONObject.parseObject(result, Map.class);
            } else {
                do {
                    // 二次查询缓存的目的是为了解决递归线程把数据同步到缓存之后，其它线程不会在自旋
                    if (StringUtils.hasText(redisTemplate.opsForValue().get(cacheKey))) {
                        return JSONObject.parseObject(result, Map.class);
                    }
//                    if (!StringUtils.hasText(redisTemplate.opsForValue().get(lockKey))){
//                        break;
//                    }//不用判断而用抢锁是防止大量线程一起出去
                    //抢锁
                    lockFlag = redisTemplate.opsForValue().setIfAbsent(lockKey, token, 10, TimeUnit.SECONDS);
                    if (lockFlag == true) {
                        deepAcquireLockTokenMap.set(token);
                    }
                } while (!lockFlag);
                //抢锁成功进行递归
                return opsDistroCacheAndCacheHit(albumId);
            }
        }
    }

    /**
     * 分布式缓存:
     * 分布式缓存未命中耗时ms：882
     * 分布式缓存命中耗时ms：493
     * 分布式缓存命中耗时ms：2
     * 分布式缓存命中耗时ms：2
     *
     * @param albumId albumId
     * @return Map<String, Object>
     */
    public Map<String, Object> opsDistroCache(Long albumId) {

        long startTime = System.currentTimeMillis();
        String cacheKey = "cache:info:albumId:" + albumId;
        String jsonFromCache = redisTemplate.opsForValue().get(cacheKey);
        if (StringUtils.hasText(jsonFromCache)) {
            System.out.println("分布式缓存命中耗时ms：" + (System.currentTimeMillis() - startTime));
            return JSONObject.parseObject(jsonFromCache, Map.class);
        }
        Map<String, Object> albumInfoFromDb = getAlbumInfoFromDb(albumId);
        redisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb));
        long endTime = System.currentTimeMillis();
        System.out.println("分布式缓存未命中耗时ms：" + (endTime - startTime));
        return albumInfoFromDb;
    }

    /**
     * 本地缓存：
     * 本地缓存未命中耗时ms：129
     * 本地缓存命中耗时ms：0
     *
     * @param albumId albumId
     * @return Map<String, Object>
     */
    public Map<String, Object> opsLocalCache(Long albumId) {
        long startTime = System.currentTimeMillis();
        String cacheKey = "cache:info:albumId:" + albumId;
        boolean isHit = localCache.containsKey(cacheKey);
        if (isHit) {
            System.out.println("本地缓存命中耗时ms：" + (System.currentTimeMillis() - startTime));
            return (Map<String, Object>) localCache.get(cacheKey);
        }
        Map<String, Object> albumInfoFromDb = getAlbumInfoFromDb(albumId);
        localCache.put(cacheKey, albumInfoFromDb);
        System.out.println("本地缓存未命中耗时ms：" + (System.currentTimeMillis() - startTime));
        return albumInfoFromDb;
    }

    @NotNull
    private HashMap<String, Object> getAlbumInfoFromDb(Long albumId) {
        HashMap<String, Object> map = new HashMap<>();

        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlumInfo(albumId);
            AlbumInfo albumInfo = albumInfoResult.getData();
            Assert.notNull(albumInfo, "远程调用获取专辑信息失败");
            map.put("albumInfo", albumInfo);
            return albumInfo;
        }, threadPoolExecutor);

        CompletableFuture<Void> albumStatVoCompletableFuture = CompletableFuture.runAsync(() -> {
            Result<AlbumStatVo> albumInfoStat = albumInfoFeignClient.getAlbumInfoStat(albumId);
            AlbumStatVo albumStatVo = albumInfoStat.getData();
            Assert.notNull(albumStatVo, "远程调用获取专辑统计信息失败");
            map.put("albumStatVo", albumStatVo);
        }, threadPoolExecutor);

        CompletableFuture<Void> albumInfoCategoryCompletableFuture = CompletableFuture.runAsync(() -> {
            Result<BaseCategoryView> albumInfoCategory = albumInfoFeignClient.getAlbumInfoCategory(albumId);
            BaseCategoryView baseCategoryView = albumInfoCategory.getData();
            Assert.notNull(baseCategoryView, "远程调用获取专辑分类信息失败");
            map.put("baseCategoryView", baseCategoryView);
        }, threadPoolExecutor);

        CompletableFuture<Void> userInfoVoCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync((albumInfo -> {
            Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfo(albumInfo.getUserId());
            UserInfoVo userInfoVo = userInfoVoResult.getData();
            Assert.notNull(userInfoVo, "远程调用获取专辑发布者信息失败");
            map.put("announcer", userInfoVo);
        }), threadPoolExecutor);

        CompletableFuture<Void> future = CompletableFuture.allOf(albumInfoCategoryCompletableFuture, albumStatVoCompletableFuture, albumInfoCategoryCompletableFuture, userInfoVoCompletableFuture);
        future.join();
//        future.get();
//        System.out.println("耗时ms：" + (System.currentTimeMillis() - startTime));
        return map;
    }

    @Override
    @SneakyThrows
    public void preHitRank() {
        Result<List<Long>> c1IdListResult = albumInfoFeignClient.getCategory1IdList();
        List<Long> c1IdList = c1IdListResult.getData();
        Assert.notNull(c1IdList, "远程调用获取一级分类Id列表失败");
        for (Long c1Id : c1IdList) {
            String[] dimensions = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};
            for (String dimension : dimensions) {
                SearchRequest searchRequest = SearchRequest.of(b -> b.index("albuminfo").query(qb -> qb.term(tmqb -> tmqb.field("category1Id").value(c1Id))).sort(sob -> sob.field(fsb -> fsb.field(dimension).order(SortOrder.Desc))).size(10));
                SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
                ArrayList<AlbumInfoIndex> albumInfoIndices = new ArrayList<>();
                for (Hit<AlbumInfoIndex> hit : response.hits().hits()) {
                    AlbumInfoIndex albumInfoIndex = hit.source();
                    albumInfoIndices.add(albumInfoIndex);
                }
                String bigKey = RedisConstant.RANKING_KEY_PREFIX + c1Id;
                //存储到redis中(使用hash)
                redisTemplate.opsForHash().put(bigKey, dimension, JSONObject.toJSONString(albumInfoIndices));
            }

        }
    }

    @Override
    public List<AlbumInfoIndexVo> findRankingList(Long c1Id, String dimension) {
        String bigKey = RedisConstant.RANKING_KEY_PREFIX + c1Id;
        String resultStr = (String) redisTemplate.opsForHash().get(bigKey, dimension);
        if (!StringUtils.hasText(resultStr)) {
            return new ArrayList<AlbumInfoIndexVo>();
        }
        return JSONObject.parseArray(resultStr, AlbumInfoIndexVo.class);
    }

    /**
     * 获取专辑Id列表
     *
     * @return List<Long>
     */
    public Result<List<Long>> getAlbumIds() {
        return albumInfoFeignClient.getAlbumIds();
    }
}
